﻿#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "ngx_c_conf.h"
#include "ngx_macro.h"
#include "ngx_global.h"
#include "ngx_func.h"
#include "ngx_c_socket.h"
#include "ngx_c_memory.h"
#include "ngx_c_lockmutex.h"
/***************************************************************
 * @file       ngx_c_socket_request.cxx
 * @brief      来数据时候的处理，当连接上有数据来的时候，
 *             本函数会被ngx_epoll_process_events()所调用
 * @author     txj
 * @version    v1
 * @return     
 * @date       2021/8/8
 **************************************************************/
void CSocekt::ngx_read_request_handler(lpngx_connection_t pConn)
{
    //是否flood攻击；
    bool isflood = false;
    /*开始收包，先收包头
    参数一：相应的连接池
    参数二：指定接收缓冲区
    参数三：指定接收长度*/
    ssize_t reco = recvproc(pConn, pConn->precvbuf, pConn->irecvlen); 
    /*这种情况认为是有问题的*/
    if(reco <= 0)
    {
        return;
    }
    /*收包头的状态，起始状态为_PKG_HD_INIT，其它地方有赋值(构造函数里)*/
    if(pConn->curStat == _PKG_HD_INIT)
    {
        /*正好收到完整包头，这里拆解包头*/
        if(reco == m_iLenPkgHeader)
        {
            //那就调用专门针对包头处理完整的函数去处理
            ngx_wait_request_handler_proc_p1(pConn,isflood); 
        }
        else/*接收的包头字节数还不够*/
        {
            /*此时接收包头处于接收状态*/
            pConn->curStat   = _PKG_HD_RECVING;
            /*收包缓冲区往后走*/
            pConn->precvbuf  = pConn->precvbuf + reco;
            /*剩余的要接收包头的长度*/
            pConn->irecvlen  = pConn->irecvlen - reco;
        }
    }
    else if(pConn->curStat == _PKG_HD_RECVING)
    {
        if(pConn->irecvlen == reco)
        {
            ngx_wait_request_handler_proc_p1(pConn, isflood);
        }
        else
        {
            pConn->precvbuf  =  pConn->precvbuf + reco;
            pConn->irecvlen  =  pConn->irecvlen - reco;
        }
    }
    else if(pConn->curStat == _PKG_BD_INIT)
    {
        if(reco == pConn->irecvlen)
        {
            if(m_floodAkEnable == 1)
            {
                isflood = TestFlood(pConn);
            }
            ngx_wait_request_handler_proc_plast(pConn,isflood);
        }
        else
        {
            pConn->curStat  = _PKG_BD_RECVING;
            pConn->precvbuf = pConn->precvbuf + reco;
            pConn->irecvlen = pConn->irecvlen - reco;
        }
    }
    else if(pConn->curStat == _PKG_BD_RECVING) 
    {
        //接收包体中，包体不完整，继续接收中
        if(pConn->irecvlen == reco)
        {
            /*Flood攻击检测是否开启*/
            if(m_floodAkEnable == 1)
            {
                isflood = TestFlood(pConn);
            }
            ngx_wait_request_handler_proc_plast(pConn, isflood);
        }
        else
        {
            //包体没收完整，继续收
            pConn->precvbuf = pConn->precvbuf + reco;
            pConn->irecvlen = pConn->irecvlen - reco;
        }
    }
    /*客户端flood服务器，则直接把客户端踢掉*/
    if(isflood == true)
    {
        ngx_log_stderr(errno,"发现客户端flood，干掉该客户端!");
        zdClosesocketProc(pConn);
    }
    return;
}
/***************************************************************
 * @file       ngx_c_socket_request.cxx
 * @brief      c：连接池中相关连接 buff：接收数据的缓冲区 buflen：要接收的数据大小
 * @author     txj
 * @version    v1
 * @return     实际接收的字节数或者-1
 * @date       2021/8/8
 **************************************************************/
ssize_t CSocekt::recvproc(lpngx_connection_t pConn,char *buff,ssize_t buflen)
{
    ssize_t n = recv(pConn->fd, buff, buflen, 0);
    /*客户端关闭了连接*/
    if(n == 0)
    {
        zdClosesocketProc(pConn);
        return -1;
    }
    /*有错误发生*/
    if(n < 0)
    {
        if(errno == EAGAIN || errno == EWOULDBLOCK)
        {
            ngx_log_stderr(errno,"CSocekt::recvproc()中errno == EAGAIN \
                || errno == EWOULDBLOCK成立");
            return -1;
        }
        else if(errno == EINTR)
        {
            ngx_log_stderr(errno,"CSocekt::recvproc()中errno \
                == EINTR成立，出乎我意料！");
            return -1;
        }
        /*下面都认为有异常：意味着我们要关闭客户端套接字要回收连接池中连接*/
        /*客户端没有正常关闭连接，比如进程崩溃*/
        if(errno == ECONNRESET)
        {
            //...
        }
        else
        {
            /*如果socket是无效的，这个会成立*/
            if(errno == EBADF)
            {
                //因为多线程，偶尔会干掉socket，所以不排除产生这个错误的可能性
            }
            else/*其它的错误*/
            {
                ngx_log_stderr(errno,"CSocekt::recvproc()中发生错误！");
            }
        }
        /*释放连接池*/
        zdClosesocketProc(pConn);
        return -1;
    }
    return n;
}
/***************************************************************
 * @file       ngx_c_socket_request.cxx
 * @brief      包头收完整后的处理
 * @author     txj
 * @version    v1
 * @return     实际接收的字节数或者-1
 * @date       2021/8/8
 **************************************************************/
void CSocekt::ngx_wait_request_handler_proc_p1(lpngx_connection_t pConn, bool &isflood)
{
    CMemory *p_memory = CMemory::GetInstance();
    LPCOMM_PKG_HEADER pPkgHeader;
    unsigned short e_pkgLen;
    /*指向收数据的缓冲区*/
    pPkgHeader = (LPCOMM_PKG_HEADER)pConn->dataHeadInfo;
    /*从包头中取出整个包的长度*/
    e_pkgLen = ntohs(pPkgHeader->pkgLen);
    /*恶意包或者错误包的判断,整个包的长度比包头的长度还行？*/
    if(e_pkgLen < m_iLenPkgHeader)
    {
        pConn->curStat = _PKG_HD_INIT;
        pConn->precvbuf = pConn->dataHeadInfo;
        pConn->irecvlen = m_iLenPkgHeader;
    }
    /*客户端发来包居然说包长度 > 29000?肯定是恶意包*/
    else if(e_pkgLen > (_PKG_MAX_LENGTH - 1000))
    {
        pConn->curStat = _PKG_HD_INIT;
        pConn->precvbuf = pConn->dataHeadInfo;
        pConn->irecvlen = m_iLenPkgHeader;
    }
    /*合法的包头，继续处理*/
    else
    {
        /*根据包的长度来分配内存，这里还要加上一个消息体的长度，
        即：消息体+包头+包体*/
        char *pTmpBuffer = (char *)p_memory->AllocMemory(m_iLenMsgHeader + e_pkgLen,false); 

        /*内存开始指针*/
        pConn->precvMemPointer = pTmpBuffer;

        /*先填写消息头内容*/
        LPSTRUC_MSG_HEADER ptmpMsgHeader = (LPSTRUC_MSG_HEADER)pTmpBuffer;
        /*收到包时的连接池中连接序号记录到消息头里来，以备将来用*/
        ptmpMsgHeader->iCurrsequence = pConn->iCurrsequence;
        ptmpMsgHeader->pConn = pConn;

        /*再填写包头内容*/
        pTmpBuffer += m_iLenMsgHeader;
        memcpy(pTmpBuffer,pPkgHeader,m_iLenPkgHeader);

        /*收到的包长度等于包头，即没有包体*/
        if(e_pkgLen == m_iLenPkgHeader)
        {
            if(m_floodAkEnable == 1)
            {
                //Flood攻击检测是否开启
                isflood = TestFlood(pConn);
            }
            ngx_wait_request_handler_proc_plast(pConn, isflood);
        } 
        else
        {
            /*开始收包体*/
            pConn->curStat = _PKG_BD_INIT;
            /*把包体的数据放到刚刚new出来的pTmpBuffer这块内存里*/
            pConn->precvbuf = pTmpBuffer + m_iLenPkgHeader;
            pConn->irecvlen = e_pkgLen - m_iLenPkgHeader;
        }
    }
    return;
}
/***************************************************************
 * @file       ngx_c_socket_request.cxx
 * @brief      收到一个完整包后的处理
 * @author     txj
 * @version    v1
 * @return     实际接收的字节数或者-1
 * @date       2021/8/8
 **************************************************************/
void CSocekt::ngx_wait_request_handler_proc_plast(lpngx_connection_t pConn,bool &isflood)
{
    /*入消息队列并触发线程处理消息*/
    if(isflood == false)
    {
        g_threadpool.inMsgRecvQueueAndSignal(pConn->precvMemPointer);
    }
    else
    {
        CMemory *p_memory = CMemory::GetInstance();
        /*这个是接收包体的时候new出来的，没有用了直接释放*/
        p_memory->FreeMemory(pConn->precvMemPointer);
    }
    /*针对该连接恢复收包状态*/
    pConn->precvMemPointer = NULL;
    pConn->curStat         = _PKG_HD_INIT;
    pConn->precvbuf        = pConn->dataHeadInfo;
    pConn->irecvlen        = m_iLenPkgHeader;
    return;
}
/***************************************************************
 * @file       ngx_c_socket_request.cxx
 * @brief      发送数据专用函数，返回本次发送的字节数
 * @author     txj
 * @version    v1
 * @return     返回 > 0，成功发送了一些字节
 *              =0，估计对方断了
 *              -1，errno == EAGAIN ，本方发送缓冲区满了
 *              -2，errno != EAGAIN != EWOULDBLOCK != EINTR ，
 * @date       2021/8/11
 **************************************************************/
ssize_t CSocekt::sendproc(lpngx_connection_t c,char *buff,ssize_t size)
{
    ssize_t    n;
    for(;;)
    {
        n = send(c->fd, buff, size, 0);
        /*大于0，说明可以发送超过，一次性发送超过或者分批发送超过*/
        if(n > 0)
        {
            return n;
        }
        /*等于0，说明是对端断开了连接或者你发送的字节数就为0，
            但是在这里不用关闭连接，在recv那里关闭就行*/
        if(n == 0)
        {
            return 0;
        }
        if(errno == EAGAIN)//EWOULDBLOCK
        {
            //内核缓冲区满
            return -1;
        }
        /*被信号中断出去了，直接循环回去继续发送*/
        if(errno == EINTR) 
        {
            ngx_log_stderr(errno,"CSocekt::sendproc()中send()失败");
        }
        else/*这里应该就是有错误了*/
        {
            return -2;
        }
    }
}
/***************************************************************
 * @file       ngx_c_socket_request.cxx
 * @brief      能走到这里，数据就是没法送完毕，要继续发送
 *             设置数据发送时的写处理函数,当数据可写时epoll通知我们
 * @author     txj
 * @version    v1
 * @return     
 * @date       2021/8/11
 **************************************************************/
void CSocekt::ngx_write_request_handler(lpngx_connection_t pConn)
{
    CMemory *p_memory = CMemory::GetInstance();
    ssize_t sendsize = sendproc(pConn,pConn->psendbuf,pConn->isendlen);
    if(sendsize > 0 && sendsize != pConn->isendlen)
    {
        pConn->psendbuf = pConn->psendbuf + sendsize;
        pConn->isendlen = pConn->isendlen - sendsize;
        return;
    }
    else if(sendsize == -1 || sendsize == -2)
    {
        ngx_log_stderr(errno,"CSocekt::ngx_write_request_handler()
            时if(sendsize == -1)成立，这很怪异。");
        return;
    }
    if(sendsize > 0 && sendsize == pConn->isendlen)
    {
        if(ngx_epoll_oper_event(
                pConn->fd,          //socket句柄
                EPOLL_CTL_MOD,      //事件类型，这里是修改【因为我们准备减去写通知】
                EPOLLOUT,           //标志，这里代表要减去的标志,EPOLLOUT：可写【可写的时候通知我】
                1,                  //对于事件类型为增加的，EPOLL_CTL_MOD需要这个参数, 0：增加   1：去掉 2：完全覆盖
                pConn               //连接池中的连接
                ) == -1)
        {
            ngx_log_stderr(errno,"CSocekt::ngx_write_request_handler()\
                中ngx_epoll_oper_event()失败。");
        }
    }
    /*能走下来的，要么数据发送完毕了，要么对端断开了*/
    p_memory->FreeMemory(pConn->psendMemPointer);
    pConn->psendMemPointer = NULL;
    /*这个值恢复了，触发下面一行的信号量才有意义*/
    --pConn->iThrowsendCount;
    if(sem_post(&m_semEventSendQueue) == -1)
    {
        ngx_log_stderr(0,"CSocekt::ngx_write_request_handler()\
            中sem_post(&m_semEventSendQueue)失败.");
    }
    return;
}
/***************************************************************
 * @file       ngx_c_socket_request.cxx
 * @brief      消息处理线程主函数，专门处理各种接收到的TCP消息(消息本身格式消息头+包头+包体)
 *             pMsgBuf：发送过来的消息缓冲区，消息本身是自解释的，通过包头可以计算整个包长
 * @author     txj
 * @version    v1
 * @return     
 * @date       2021/8/11
 **************************************************************/
void CSocekt::threadRecvProcFunc(char *pMsgBuf)
{
    return;
}
